查看原文
其他

当我们设计一个专用处理器的时候我们在干什么?(指令集)

2017-02-27 T.S. StarryHeavensAbove

写了第一篇以后,感觉坑挖的有点深了,要把问题尽量说的清楚一些,可能得搞个系列文章了。


由于专用处理器这个名称可以用在很多地方,我想再明确一下本文说的专用处理器设计的范围:1. 我们主要讨论IP级的设计,也就是说专用处理器设计最终作为一个SoC(System-on-Chip)芯片中的IP出现。它需要和其它模块合作完成整个芯片要实现的功能(下图)。2. 我们主要讨论的专用处理器是要运行程序的,有自己的指令集(可能很简单),需要有存储程序的空间和读取指令,执行指令的机制。


专用处理器虽然强调专用二字,但实际上还是一个处理器。因此,设计一个专用处理器,和设计通用处理器的内容和过程类似。简单来说,这一过程覆盖指令集(ISA),微结构(硬件)和工具链(软件)的设计和实现。这一篇先讨论一下指令集的问题。


对专用处理器来说,指令集的设计直接反映了对应用需求的理解。比如,第一篇中介绍的例子,如果我们的专用处理器只是为了加速256点的FFT用的,那么指令集里只有FFT指令就够用了。考虑到数据搬移的需求,再加上几条数据搬移的指令,比如读取数据,写回数据。于是可以得到一个有三条指令的指令集(fft,load,store)。这个处理器执行的汇编(Assembly)程序大概就是这样的

当然,这里做了很多简化,但看起来这个指令集已经可以work了。同时,这也是一个“高效”的设计,没有多余的东西。如果你要循环做8次fft,那么可能需要增加一条loop(循环)指令,要不然你就得把上面的代码copy八次。加了loop指令,汇编代码可能是下面这样。

设计指令集除了确定每条指令的功能和操作数(比如上面的例子里的寄存器的名字,循环次数等等),还有一个重要工作就是设计指令编码。像“load”这样的指令名称只是个抽象的描述,而处理器的硬件看到的实际上是二进制的指令编码。还是上面这个例子,由于我们的指令集有4条指令,则需要2个比特来区分,比如,00:fft;01:load:10;store;11:loop。再具体看load指令,还需要几个bit来说明目标寄存器,假设一共有8个寄存器(R0-R7),区分他们又需要3个bit;另外需要一个bit来说明数据的来源:是一个立即数还是来自内存;如果来源是内存,还需要几个bit表示内存地址,或者指示存放内存地址的寄存器编号,等等。你会发现,可能对于一个load指令,一共需要32个bit的指令编码。上面的汇编程序第三行对应的机器码可能是这样的。


最后,把所有的情况列出来,就形成一个指令和指令编码的列表。这就是一个完整的指令集架构(ISA)了。实际上的ISA当然比这个复杂很多,但不管多复杂,主要也就是这几大类功能:第一是执行运算或处理功能的,比如算数运算;第二类是控制程序流的,比如循环,分支和跳转;第三类实现数据搬移的,比如内存到寄存器,寄存器之间;最后还有一些辅助功能,比如debug,中断,cache之类的指令。这里就不展开讲了,有兴趣的同学可以自己研究一下现在挺火的开源指令集RISC-V。个人感觉,仔细看明白一个好的指令集的设计思想,比看教科书收获要多得多。


以上算是指令集的背景知识吧,回到设计专用处理器的问题。当我们有了一个应用需求,怎么来设计和优化指令集呢?我想这个问题可能很难有个统一的答案,这里就说说我的个人经验吧。


1. 确定评价标准

我们设计专用处理器都是有明确目的性的,先把目标弄清楚至关重要。评价一般的通用处理器有一些成熟的benchmark,比如Dhrystone,Coremark等等,相当于测试手机硬件的跑分软件,只不过是针对处理器的。还有一些benchmark更面向专用领域,比如多媒体,DSP,或者针对特殊结构的,比如cache。

那么评价专用处理器的标准是什么呢?很简单,目标应用。所以,最好在开始设计之前,就把目标应用定量化如果目标应用已经有程序代码,就可以直接用这些程序代码做benchmark,来评价你的设计。如果还没有完整的应用程序代码,最好也要把关键算法部分写出来。当然,这一条不只是针对指令集,而是针对完整的专用处理器,包括工具链的设计(后续再介绍)。对于专用处理器设计来说,评价标准一般是有限明确,是正是它能够在一个领域做的比通用处理器效率高的最重要因素。


2. 选择一个参考指令集作为基础

前面一篇文章说过,做专用处理器相当于“重新发明轮子”,存在很大的风险。那么我们能否把问题变成“重新设计轮子”呢(是不是看起来要简单了很多)?在多数情况下都是可以,而且有效的。对于大部分应用来说,其合理的指令集都需要一些基本的指令,这一部分完全可以参考已有的设计。这样可以大大降低设计的风险。

比如,我们现在有一个应用,在通用处理以外需要大量的FFT操作。一种方法是自己设计一套指令集,即包括通用指令,又包括特殊指令,比如专门的蝶形运算(butterfly)操作。另一种方法是参考一个成熟的指令集,比如RISC-V这个很好的开源指令集架构,在它的基础上做优化工作,增加butterfly指令,并减少一些不常用的指令(当然前提是这种改动不触及RISC-V的知识产权规则)。相比第一种方法,这样显然风险要小的多。还有一个好处,我们可以重用参考处理器的工具链,或者只要少量改动,进一步降低的工作量。

结合第1点,一般我们可以先把目标应用的程序在参考的处理器上跑一下,做一些评估,找到瓶颈。针对瓶颈问题设计或改进指令集,以及后面要介绍的微结构和工具链。这也说明在开始的时候就有一个明确的评价标准的好处。


3. 充分利用工具

实际上,不管是指令集还是微结构,设计和优化过程就是一个在优化目标指导下的设计空间探索问题。如果你足够厉害,你当然可以采用“pencil & paper”的方法。但对于我们大部分设计者来说,人肉探索这个设计空间几乎是不可能的。能不能充分利用工具帮忙,往往决定我们是不是能够尽快得出一个比较好的设计。在第2点里,我们先把应用在一个现有的处理器上跑一下,然后根据profiling结果做优化设计,实际就是借助工具帮助我们分析问题。

如果你有足够的资金,还可以借助一些商用的工具。比如,有的DSP IP支持一些扩展和定制的功能和工具,你可以在一个基础设计上针对你的应用设计你自己的专用处理器;还有专门设计ASIP(专用指令处理器)的工具,你甚至可以用一些高层次的语言来描述处理器,工具自动生成工具链和硬件设计(RTL代码),并且评估设计的好坏,帮忙进行优化。当然,这些IP或者工具一般价格昂贵,也有一定的技术门槛。以后有机会在详细介绍吧。


感觉坑是越挖越深,也只能想到哪儿写到哪儿了。希望能坚持把坑填平吧。

T.S.

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存